package org.fujene.communicate.repli;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.TreeSet;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import org.fujene.structure.ExtendQueue;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.LookupPointer;
import org.fujene.structure.Node;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.BooleanType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.NilType;
import org.msgpack.object.RawType;
import org.msgpack.rpc.Request;

public class ReplicatePeerHandler{
	private static final int OUTER = ReplicatePeerSystem.OUTER;
	private static final int INNER = ReplicatePeerSystem.INNER;
	private Logger logger;
	private ReplicatePeerSystem parentSystem;
	private int contentNum;
	private int supplementNum;

	public ReplicatePeerHandler(Logger logger, ReplicatePeerSystem parentSystem, int contentNum,
			int supplementNum){
		this.logger = logger;
		this.parentSystem = parentSystem;
		this.contentNum = contentNum;
		this.supplementNum = supplementNum;
	}

	/**
	 * ノードチェーンを送信します。
	 * @param req
	 *            ... 0: (IntegerType/int)ノードサークル種別
	 */
	public void nodeList(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if(objs.length < 1 || !objs[0].isIntegerType()){
			req.sendError(RawType.create("ERROR(nodeList): Argument is invalid"));
			logger.severe("ERROR(nodeList): Argument is invalid");
			return;
		}

		MessagePackObject[] output = null;
		int count = 0;

		TreeSet<Node> currentInfos = new TreeSet<Node>(parentSystem.getNode(objs[0].asInt()));
		output = new MessagePackObject[2 * currentInfos.size()];

		for(Node eachPeer: currentInfos){
			output[count++] = RawType.create(eachPeer.getAddr());
			output[count++] = IntegerType.create(eachPeer.getHash());
		}

		logger.finest("circleList - called.");

		req.sendResult(ArrayType.create(output));
	}

	/**
	 * ノードを登録します。
	 * @param req
	 *            ... 0: (RawType/String)IPアドレス, 1: (IntegerType/int)本文チェーンハッシュ,
	 *            2: (IntegerType/int)辞書チェーンハッシュ
	 */
	public void register(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if(objs.length < 3 || (!objs[0].isRawType() && !objs[1].isIntegerType() && !objs[2].isIntegerType())){
			req.sendError(RawType.create("ERROR(register): Argument is invalid"));
			logger.severe("ERROR(register): Argument is invalid");
			return;
		}

		parentSystem.addReplication(OUTER, new Node(objs[0].asString(), objs[1].asLong()));
		parentSystem.addReplication(INNER, new Node(objs[0].asString(), objs[2].asLong()));

		logger.finest("register - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * ノードを削除します。
	 * @param req
	 *            ... 0: (IntegerType/int)ノード種別, 1: (IntegerType/int)削除ハッシュ
	 */
	public void delete(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if(objs.length < 2 || (!objs[0].isIntegerType() && !objs[1].isIntegerType())){
			req.sendError(RawType.create("ERROR(delete): Argument is invalid"));
			logger.severe("ERROR(delete): Argument is invalid");
			return;
		}

		parentSystem.deleteReplication(objs[0].asInt(), new Node("Dummy", objs[1].asInt()));

		logger.finest("delete - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * データを送信します。(未使用)
	 * @param req
	 *            ... 0: (IntegerType/int)サークル種別, 1: (ArrayType)データ(単語[本文], ID,
	 *            単語[本文], ID, ...)
	 */
	public void sendData(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		int i = 0;
		if(objs.length < 2 || (!objs[0].isIntegerType() && !objs[1].isArrayType())){
			req.sendError(RawType.create("ERROR(sendData): Argument is invalid"));
			logger.severe("ERROR(sendData): Argument is invalid");
			return;
		}

		int type = objs[0].asInt();
		MessagePackObject[] datas = objs[1].asArray();
		if(type == OUTER)
			for(; i < datas.length; i += 4)
				parentSystem.invokeIndexOuter(datas[i].asString(), datas[i + 1].asInt(),
						datas[i + 2].asLong(), datas[i + 3].asInt());
		else{
			String[] contents = new String[contentNum];
			String[] supplements = new String[supplementNum];
			int j;
			long ID;
			while(i < datas.length){
				ID = datas[i].asLong();
				for(j = 0; j < contentNum; i++, j++){
					contents[j] = datas[i].asString();
				}
				for(j = 0; j < supplementNum; i++, j++){
					supplements[j] = datas[i].asString();
				}
				parentSystem.invokeIndexInner(new ExtendQueue(contents, supplements, ID));
			}
		}
		logger.finest("sendData - called.");

		req.sendResult(NilType.create());
	}

	// 溜め込み用
	private String recieverIP = null;
	private int dataType = -1;
	private ArrayList<?> dataList = null;
	private Future<ArrayList<ExtendTerm>> termListFuture = null;
	private Future<ArrayList<ExtendQueue>> contentListFuture = null;

	/**
	 * 指定した範囲のデータを取得します。
	 * @param req
	 *            ... 0: (IntegerType/int)データ番号, 1:
	 *            (RawType/String)自分のIPアドレス</p>
	 * @param 戻り値
	 *            ... (共通) 0: 単語[本文]
	 */
	public void getData(Request req){
		try{
			MessagePackObject obj = req.getArguments();
			MessagePackObject[] objs = obj.asArray();

			if(objs.length < 2 || (!objs[0].isIntegerType() && !objs[1].isRawType())){
				req.sendError(RawType.create("ERROR(getData): Invalid argument"));
				logger.severe("ERROR(getData): Invalid argument");
				return;
			}else if(dataList == null){
				req.sendError(RawType.create("ERROR(getData): Call \"getDataSize\" first"));
				logger.severe("ERROR(getData): Call \"getDataSize\" first");
				return;
			}else if(!objs[1].asString().equals(recieverIP)){
				// 他のIPに送信中
				req.sendResult(BooleanType.create(false));
				return;
			}

			int index = objs[0].asInt();
			int fieldLength, outputLength;
			MessagePackObject[] output;
			if(dataType == OUTER){
				ArrayList<ExtendTerm> termList = termListFuture.get();
				parentSystem.iamIndexable();
				fieldLength = termList.get(index).firstIDs.length;
				outputLength = 1 + 3 * fieldLength;
				output = new MessagePackObject[outputLength];
				output[0] = RawType.create(termList.get(index).term);
				for(int i = 0; i < fieldLength; i++){
					output[1 + 3 * i] = IntegerType.create(termList.get(index).firstIDs[i]);
					// ポインタも追加
					if(termList.get(index).invIndex[i] != null){
						output[2 + 3 * i] = IntegerType.create(termList.get(index).invIndex[i].block);
						output[3 + 3 * i] = IntegerType.create(termList.get(index).invIndex[i].point);
					}else{
						output[2 + 3 * i] = IntegerType.create(-1);
						output[3 + 3 * i] = IntegerType.create(-1);
					}
				}
			}else{
				ArrayList<ExtendQueue> contentList = contentListFuture.get();
				parentSystem.iamIndexable();
				outputLength = 1 + contentList.get(index).contents.length
						+ contentList.get(index).appendices.length;
				output = new MessagePackObject[outputLength];

				output[0] = IntegerType.create(contentList.get(index).ID);
				// 本文

				for(int i = 0; i < contentList.get(index).contents.length; i++)
					output[i + 1] = RawType.create(contentList.get(index).contents[i]);
				// 付属文章
				for(int i = 0; i < contentList.get(index).appendices.length; i++)
					output[i + 1 + contentList.get(index).contents.length] = RawType.create(contentList
							.get(index).appendices[i]);
			}

			req.sendResult(ArrayType.create(output));
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	 * 指定した範囲にあるデータサイズを取得し、データ転送の準備をします。
	 * @param req
	 *            ... 0: (IntegerType/int)チェーン種別, 1: (IntegerType/int)ハッシュ開始位置,
	 *            2: (IntegerType/int)ハッシュ終了位置, 3: (RawType/String)自分のIPアドレス,
	 *            4: (BooleanType/boolean)スナップショット取得ならTrue
	 */
	public void getDataSize(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();

		if(objs.length < 5
				|| (!objs[0].isIntegerType() && !objs[1].isIntegerType() && !objs[2].isIntegerType()
						&& !objs[3].isRawType() && !objs[4].isBooleanType())){
			req.sendError(RawType.create("ERROR(getDataSize): Invalid argument"));
			logger.severe("ERROR(getDataSize): Invalid argument");
			return;
		}

		recieverIP = objs[3].asString();
		dataType = objs[0].asInt();

		// スナップショット取得
		if(objs[4].asBoolean()){
			// インデックス中止
			parentSystem.stopIndexable();
			contentListFuture = parentSystem.getContentSnapshot(objs[1].asLong(), objs[2].asLong(),
					objs[0].asInt());
			termListFuture = parentSystem.getTermSnapshot(objs[1].asLong(), objs[2].asLong(), 0);
		}
		logger.finest("getDataSize - called.");

		req.sendResult(IntegerType.create(dataList.size()));
	}

	/**
	 * データ転送を終了します。
	 * @param req
	 *            ... 0: (RawType/String)自分のIPアドレス
	 */
	// FIXME: 手動でロックを外せる or タイムアウト, タイムアウト時間はどうする？
	public void endDataSend(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();

		if(objs.length < 1 || (!objs[0].isRawType())){
			req.sendError(RawType.create("ERROR(endDataSend): Invalid argument"));
			logger.severe("ERROR(endDataSend): Invalid argument");
			return;
		}

		logger.finest("endDataSend - called.");

		if(objs[0].asString().equals(recieverIP)){
			recieverIP = null;
			dataList.clear();
			dataList = null;
			dataType = -1;
			req.sendResult(BooleanType.create(true));
		}else req.sendResult(BooleanType.create(false));
	}
	
	/**
	 * 強制的にデータ転送を中止します。
	 * @param req
	 */
	public void forceStop(Request req) {
		recieverIP = null;
		dataList.clear();
		dataList = null;
		dataType = -1;
		
		req.sendResult(NilType.create());
	}

	/**
	 * 指定した範囲のデータを削除します。（未使用）
	 * @param req
	 *            ... 0: (IntegerType)文章タイプ, 1: (IntegerType)ハッシュ開始位置, 2:
	 *            (IntegerType)ハッシュ終了位置
	 */
	public void deleteData(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if(objs.length < 3
				|| (!objs[0].isIntegerType() && !objs[1].isIntegerType() && !objs[2].isIntegerType())){
			req.sendError(RawType.create("ERROR(deleteData): Invalid argument"));
			logger.severe("ERROR(deleteData): Invalid argument");
			return;
		}

		MessagePackObject[] output = null;

		logger.finest("deleteData - called.");

		req.sendResult(ArrayType.create(output));
	}

	/**
	 * 指定した範囲のデータを削除します。（未使用）
	 * @param req
	 *            ... 0: (IntegerType/long)文章ID
	 */
	public void deleteContent(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if(objs.length < 1 || (!objs[0].isIntegerType())){
			req.sendError(RawType.create("ERROR(deleteContent): Invalid argument"));
			logger.severe("ERROR(deleteContent): Invalid argument");
			return;
		}

		parentSystem.invokeDelete(objs[0].asLong());

		logger.finest("deleteContent - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * 生存しているかどうか返します。
	 * @param req
	 *            ... 引数なし
	 */
	public void isLive(Request req){
		// logger.finest("isLive - called.");

		req.sendResult(RawType.create("Live"));
	}

	/**
	 * 送られた文章をインデックスに追加します。
	 * @param req
	 *            ... 0: (IntegerType)文章ID 1～: (RawType)文章
	 */
	public void indexContent(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		MessagePackObject[] argobjs = null;
		int i = 0;
		if(objs.length < 2 || (!objs[0].isArrayType() && !objs[1].isIntegerType())){
			req.sendError(NilType.create());
			logger.severe("ERROR(indexContent): Invalid argument");
			return;
		}

		long ID = objs[1].asLong();
		argobjs = objs[0].asArray();
		String[] contents = new String[contentNum];
		String[] supplements = new String[supplementNum];
		for(i = 0; i < contentNum; i++){
			contents[i] = argobjs[i].asString();
		}
		for(; i < contentNum + supplementNum; i++){
			supplements[i - contentNum] = argobjs[i].asString();
		}
		parentSystem.invokeIndexInner(new ExtendQueue(contents, supplements, ID));

		// System.out.println("indexContent - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * 送られた単語をインデックスに追加します。
	 * @param req
	 *            ... 0: (RawType)単語 1: (IntegerType)文章ID 2: (IntegerType)文章タイプ
	 */
	public void indexTerm(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if(objs.length < 3
				|| (!objs[0].isRawType() && !objs[1].isIntegerType() && !objs[2].isIntegerType() && !objs[3]
						.isIntegerType())){
			req.sendError(RawType.create("ERROR(indexTerm): Invalid argument"));
			logger.severe("ERROR(indexTerm): Invalid argument");
			return;
		}

		parentSystem.invokeIndexOuter(objs[0].asString(), objs[1].asInt(), objs[2].asLong(), objs[3].asInt());

		// System.out.println("indexTerm - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * 送られた単語をインデックスに追加します。
	 * @param req
	 *            ... 0: (RawType/String)単語 1: (IntegerType/int)文章タイプ
	 */
	public void searchTerm(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if(objs.length < 2 || (!objs[0].isRawType() && !objs[1].isIntegerType())){
			req.sendError(RawType.create("ERROR(searchTerm): Invalid argument"));
			logger.severe("ERROR(searchTerm): Invalid argument");
			return;
		}

		ExtendTerm result = parentSystem.invokeSearch(objs[0].asString(), objs[1].asInt());

		logger.finest("searchTerm - called.");

		if(result == null)
			req.sendResult(NilType.create());
		else req.sendResult(RawType.create(result.createByteArray()));
	}

	/**
	 * 本文を送信します。
	 * @param req
	 *            ... 0: (IntegerType/long)文章ID, 1: インデックス
	 */
	public void getContent(Request req){
		try{
			MessagePackObject obj = req.getArguments();
			MessagePackObject[] objs = obj.asArray();
			int i;
			if(objs.length < 2 || (!objs[0].isIntegerType() && !objs[1].isIntegerType())){
				req.sendError(RawType.create("ERROR(getContent): Invalid argument"));
				logger.severe("ERROR(getContent): Invalid argument");
				return;
			}

			logger.finest("getContent - called.");

			ExtendQueue content = parentSystem.lookupContent(objs[0].asLong(), objs[1].asInt());
			MessagePackObject[] retobjs = new MessagePackObject[content.contents.length
					+ content.appendices.length];
			for(i = 0; i < content.contents.length; i++){
				retobjs[i] = RawType.create(content.contents[i]);
			}
			for(i = 0; i < content.appendices.length; i++){
				retobjs[i + content.contents.length] = RawType.create(content.appendices[i]);
			}

			req.sendResult(ArrayType.create(retobjs));
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	 * 本文のIDポインタを取得します。
	 * @param req
	 *            ... 0: (IntegerType/int)開始ポインタ(block), 1:
	 *            (IntegerType/int)開始ポインタ(point), 2: (IntegerType/long)開始ID, 3:
	 *            (IntegerType/int)長さ<br>
	 *            戻り値 ... 0: 次の開始ポインタ(block), 1: 次の開始ポインタ(point), 2, 3, 4,...:
	 *            ID
	 */
	public void getList(Request req){
		try{
			MessagePackObject obj = req.getArguments();
			MessagePackObject[] objs = obj.asArray();
			if(objs.length < 4
					|| (!objs[0].isIntegerType() || !objs[1].isIntegerType() || !objs[2].isIntegerType() || !objs[3]
							.isIntegerType())){
				req.sendError(RawType.create("ERROR(getList): Invalid argument"));
				logger.severe("ERROR(getList): Invalid argument");
				return;
			}

			LookupPointer ptr = new LookupPointer(objs[0].asInt(), objs[1].asInt(), objs[2].asLong());
			LinkedHashMap<Long, Integer> list = parentSystem.getContentsByHash(ptr, objs[3].asInt());

			MessagePackObject[] retval = new MessagePackObject[2 * list.size() + 3];
			retval[0] = IntegerType.create(ptr.block);
			retval[1] = IntegerType.create(ptr.point);
			retval[2] = IntegerType.create(ptr.firstID);
			int i = 0;
			for(Entry<Long, Integer> entry: list.entrySet()){
				retval[2 * i + 3] = IntegerType.create(entry.getKey().longValue());
				retval[2 * i + 4] = IntegerType.create(entry.getValue().intValue());
				i++;
			}

			req.sendResult(ArrayType.create(retval));
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	 * ノード参加に必要な情報を各種送信します。 (BlockNum, BlockSize, Replication, IndexMode)
	 * @param req
	 *            ... 引数なし
	 */
	public void getInfo(Request req){
		try{
			logger.finest("getInfo - called.");
			req.sendResult(parentSystem.putInfo());
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	 * インデックスキューにたまっている数を返します。
	 * @param req
	 *            ... 引数なし
	 */
	public void getIndexQueue(Request req){
		logger.finest("getIndexQueue - called.");
		req.sendResult(IntegerType.create(parentSystem.getQueueSize(0)));
	}

	/**
	 * サーチキューにたまっている数を返します。
	 * @param req
	 *            ... 引数なし
	 */
	public void getSearchQueue(Request req){
		logger.finest("getSearchQueue - called.");
		req.sendResult(IntegerType.create(parentSystem.getQueueSize(1)));
	}
}
